/**
 * @file clientUDP.c
 *
 * @date Mar 31, 2012
 * @author Francesco Corvino
 */
#include <stdio.h>      /* standard C i/o facilities */
#include <stdlib.h>     /* needed for atoi() */
#include <unistd.h>  	/* defines STDIN_FILENO, system calls,etc */
#include <string.h>
#include <sys/types.h>  /* system data type definitions */
#include <sys/socket.h> /* socket specific definitions */
#include <netinet/in.h> /* INET constants and stuff */
#include <arpa/inet.h>  /* IP address conversion stuff */
#include <netdb.h>	/* gethostbyname */
#include "../src/MemObj/dynArray.h"

#define MAXBUF 1024*1024
#define CHATSTART_MESSAGE "start"

typedef struct sockaddr Address;

typedef struct addrinfo AddressInfo;

/**
 * @brief Struttura che specifica un'utente in chat.
 */
typedef struct {
	char name[20];
	Address address;
} Utente;

DynArray utenti;

char controllClientAddress = 1;

char client_Name[30] = "localhost";

char client_Port[20] = "0";

char client_Address[50] = "127.0.0.1";

char RegServer_Address[50] = "localhost";

char RegServer_Port[20] = "45678";

int getStrAddress(int family,Address *addr,char *addrstr,size_t dim){
	void *ptr = NULL;
	switch (family) {
    case AF_INET:
    	ptr = &((struct sockaddr_in *) addr)->sin_addr;
    	break;
    case AF_INET6:
    	ptr = &((struct sockaddr_in6 *) addr)->sin6_addr;
    	break;
	}
    inet_ntop (family, ptr, addrstr, dim);
    return strlen(addrstr);
}

int getStrPort(int family,Address *addr,char *portstr, size_t dim){
	int port;
	switch (family) {
    case AF_INET:
    	port = ((struct sockaddr_in *) addr)->sin_port;
    	break;
    case AF_INET6:
    	port = ((struct sockaddr_in6 *) addr)->sin6_port;
    	break;
	}
	return sprintf(client_Port,"%d", htons(port));
}

/**
 * @brief Effettua il confronto tra due utenti.
 *
 * @param a
 * @param b
 * @return
 */
int Utente_cmp(const void * a, const void * b){
	Utente *A = (Utente*) a; Utente *B = (Utente*) b;
	return memcmp(&(A->address),&(B->address),sizeof(Address));
}

/**
 * @brief Attende un'input tra due file descriptor.
 *
 * @return restituisce il primo file descriptor che è pronto per la lettura.
 */
int WaitInput(int fd0, int fd1) {
	struct timeval tv;
	tv.tv_sec = 1000;
	tv.tv_usec = 0;
	fd_set fdsetread;
	FD_ZERO(&fdsetread);
	FD_SET(fd0, &fdsetread);
	FD_SET(fd1, &fdsetread);
	int maxfd = (fd0 > fd1) ? (fd0) : (fd1);
	select(maxfd + 1, &fdsetread, NULL, NULL, &tv);
	if (FD_ISSET(fd0,&fdsetread))
		return fd0;
	else if (FD_ISSET(fd1,&fdsetread))
		return fd1;
	return -1;
}

/**
 * @brief Funzione che gestisce la scrittura sullo standard output.
 */
void WriteMsg(char* name, char *msg) {
	printf("%s: %s \n", name, msg);
}

/**
 * @brief Inizializza i dati di rete restituendo il socket su cui effettuare
 * le comunicazioni.
 */
int StartNetData() {
	int ld;

	AddressInfo *res, *hostinfo, hints;
	/// - Imposta i valori per creare il socket
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	//hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM;
	//hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags |= AI_CANONNAME; // AI_PASSIVE | use my IP

	/// - Recupera le informazioni sull'indirizzo da utilizzare
	if ((getaddrinfo(client_Address, NULL, &hints, &hostinfo)) != 0)  {
		exit(1);
	}

	/// - Modificare l'assegnamento (o inserire il for) per prendere altri indirizzi dal primo restituito
	for(res = hostinfo; res != NULL; res = res->ai_next){

		/// - controlla se richiesto l'indirizzo
		if(controllClientAddress){
			char addressThis[100];
			getStrAddress(res->ai_family,res->ai_addr,addressThis,100);
			if(strcmp(addressThis,client_Address)!=0) {
				printf("Scartato l'indirizzo <%s> (%s), perchè richiesto <%s>!\n",
						addressThis, res->ai_canonname, client_Address);
				continue;
			}
		}

		/// - Crea il socket
		if ((ld = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) {
			printf("Problem creating socket\n");
			exit(1);
		}

		/// - Effettua il binding
		if (bind(ld, res->ai_addr, res->ai_addrlen) == -1) {
			close(ld);
			printf("Problem binding\n");
			exit(1);
		}
		break;
	}

	if(res==NULL) {
		printf("Errore nel creare la connessione! Controllare le impostazioni.\n");
		exit(1);
	}

	Address skaddr; int length = sizeof(skaddr);
	if (getsockname(ld, (Address *) &skaddr, &length) < 0) {
		printf("Error getsockname\n");
		exit(1);
	}
	getStrAddress(skaddr.sa_family,&skaddr,client_Address,sizeof client_Address);
	getStrPort(skaddr.sa_family,&skaddr,client_Port,sizeof client_Port);

	printf("The chat client is listen to %s at the UDP port number %s\n",
			client_Address, client_Port);
	return ld;
}

/**
 * @brief Avvia un echo server sul socket passato.
 *
 * @param sd socket descriptor.
 */
void echo(int sd) {
	int len, n;
	char bufin[MAXBUF];
	struct sockaddr_in remote;

	// need to know how big address struct is, len must be
	// set before the call to recvfrom!!!
	len = sizeof(remote);

	while (1) {
		/* read a datagram from the socket (put result in bufin) */
		n = recvfrom(sd, bufin, MAXBUF, 0, (struct sockaddr *) &remote, &len);

		/* print out the address of the sender */
		printf("Got a datagram from %s port %d\n", inet_ntoa(remote.sin_addr),
				ntohs(remote.sin_port));

		if (n < 0) {
			perror("Error receiving data");
		} else {
			printf("GOT %d BYTES\n", n);
			/* Got something, just send it back */
			sendto(sd, bufin, n, 0, (struct sockaddr *) &remote, len);
		}
	}
}

/**
 * @brief Registra il client al server
 * @param sd
 */
void regToServer(int sd){
	/// - Compone il messaggio di registrazione
	char regMsg[1000];int lenRegMsg;
	sprintf(client_Name, "client-%d", getpid());
	lenRegMsg = sprintf(regMsg, "request=register;name=%s;port=%s;address=%s;",
			client_Name, client_Port, client_Address);
	/// - Ricava le informazioni sul server
	AddressInfo hints,*servinfo;
	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_UNSPEC; // set to AF_INET to force IPv4
	hints.ai_socktype = SOCK_DGRAM;

	if(getaddrinfo(RegServer_Address,RegServer_Port,&hints,&servinfo)==-1){
		printf("Problem search info for Register Server\n");
		exit(1);
	}
	/// - Crea la connessione con il server prendendo il primo indirizzo valido
	int ld = -1;
	if ((ld = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol)) < 0) {
		printf("Problem creating socket for Register Server\n");
		exit(1);
	}
	/// - Invia il messaggio di registrazione al server.
	if(sendto(ld,regMsg,lenRegMsg,0,(Address*) servinfo->ai_addr,
			servinfo->ai_addrlen)>0){
		printf("Inviato il messaggio <%s> al server\n",regMsg);
	} else {
		printf("Errore.. impossibile registrarsi al server!\n");
	}
	/// - Libera la memoria allocata e chiude il socket per comunicare con il server
	freeaddrinfo(servinfo);
	close(ld);
}

/**
 * @brief Si occupa di ricevere un messaggio identificando l'utente che ha scritto.
 *
 * @param sd Socket desciptor da cui ricevere il messaggio.
 * @param bufin buffer su cui scrivere il messaggio ricevuto.
 * @return puntatore all'utente che ha scritto il messaggio.
 */
Utente * ChatRecv(int sd, char* bufin){
	Address remote; int len= sizeof(Address);
	/// - riceve il messaggio.
	int n = recvfrom(sd, bufin, MAXBUF, 0, (Address*) &remote, &len);
	/// - controlla se il messaggio è stato ricevuto correttamente.
	if(n<0){
		printf("Non riesco a leggere il messaggio!\n");
		return NULL;
	}
	/// - salva l'indirizzo da cui è stato ricevuto il messaggio.
	Utente nuovo;
	memcpy(&(nuovo.address),&remote, sizeof(Address));
	/// - cerca l'utente nella lista degli utenti registrati.
	Utente *ret = DynArray_get(&utenti, &nuovo);
	/// - controlla se l'utente è nuovo e in questo caso:
	if(ret == NULL) {
		/// + ricava il nome dal messaggio di presentazione.
		len = strlen(CHATSTART_MESSAGE);
		if(strncmp(bufin,CHATSTART_MESSAGE,len)==0) {
			strcpy(nuovo.name,bufin+len+1);
		}
		/// + controlla la validità del nome
		if(strlen(nuovo.name)<1)
			sprintf(nuovo.name, "utente%d",utenti.numelement +1);
		/// + registra il nuovo utente.
		DynArray_add(&utenti,&nuovo);
		/// + restituisce il puntatore
		return DynArray_get(&utenti, &nuovo);
	}
	return ret;
}

/**
 * @brief Invia un messaggio al client.
 *
 * @param sd
 * @param msg
 * @param bytes
 * @param client
 */
void ChatSend(int sd,char* msg, int bytes, Utente * client){
	int len = sizeof(Address);
	if(sendto(sd, msg, bytes, 0,(Address*) &(client->address), len)>0){
		printf("Messaggio inviato a %s \n",client->name);
	} else {
		printf("Errore nell'invio del messaggio a %s!\n",client->name);
	}
}

/**
 * @brief server main routine
 *
 * @param argc richiede non necessariamente 2 parametri
 * @param argv primo valore dell'array->indirizzo del client da utilizzare;
 * secondo-> indirizzo del server di registrazione.
 *
 * @return
 */
int main(int argc, char * argv[]) {
	int socketFd;
	char msg[MAXBUF];
	DynArray_init(&utenti, Utente_cmp, sizeof(Utente));

	/// - se fornito considera come primo parametro l'indirizzo del server
	if(argc>1){
		strcpy(client_Address,argv[1]);
		controllClientAddress = 1;
	} else controllClientAddress = 0;

	/// - se fornito considera come secondo parametro l'indirizzo da registrare
	if(argc>2){
		strcpy(RegServer_Address,argv[2]);
	}

	/// - apre il socket su cui mettersi in attesa per la chat
	socketFd = StartNetData();

	/// - invia i dati al server
	regToServer(socketFd);

	/// - Avvia il loop per rispondere ai messaggi
	while(1){
		memset(msg,0,MAXBUF);
		int fdToRead = WaitInput(socketFd,STDIN_FILENO);
		if(fdToRead==socketFd) {
			// lettura dalla rete
			Utente * utente = ChatRecv(socketFd,msg);
			WriteMsg(utente->name, msg);
		} else {
			// lettura dal client
			fgets(msg, MAXBUF, stdin);
			Utente * utente = NULL; int index=0;
			DYNARRAY_FOREACH(&utenti,&index,utente){
				ChatSend(socketFd,msg,strlen(msg),utente);
			}
			puts("---------------- Messaggio inviato -----------------\n");
		}
	}
	return (0);
}

